// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.CommandLine;
using System.Text.Json;
using Microsoft.DotNet.Cli.Commands.Workload.Install;
using Microsoft.DotNet.Cli.Commands.Workload.Install.WorkloadInstallRecords;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using Microsoft.TemplateEngine.Cli.Commands;

namespace Microsoft.DotNet.Cli.Commands.Workload.List;

internal class WorkloadListCommand : WorkloadCommandBase
{
    private readonly bool _includePreviews;
    private readonly bool _machineReadableOption;
    private readonly IWorkloadManifestUpdater _workloadManifestUpdater;
    private readonly WorkloadInfoHelper _workloadListHelper;

    public WorkloadListCommand(
        ParseResult parseResult,
        IReporter reporter = null,
        IWorkloadInstallationRecordRepository workloadRecordRepo = null,
        string currentSdkVersion = null,
        string dotnetDir = null,
        string userProfileDir = null,
        string tempDirPath = null,
        INuGetPackageDownloader nugetPackageDownloader = null,
        IWorkloadManifestUpdater workloadManifestUpdater = null,
        IWorkloadResolver workloadResolver = null
    ) : base(parseResult, CommonOptions.HiddenVerbosityOption, reporter, tempDirPath, nugetPackageDownloader)
    {
        _machineReadableOption = parseResult.GetValue(WorkloadListCommandParser.MachineReadableOption);

        var resolvedReporter = _machineReadableOption ? NullReporter.Instance : Reporter;
        _workloadListHelper = new WorkloadInfoHelper(
            parseResult.HasOption(SharedOptions.InteractiveOption),
            Verbosity,
            parseResult?.GetValue(WorkloadListCommandParser.VersionOption) ?? null,
            VerifySignatures,
            resolvedReporter,
            workloadRecordRepo,
            currentSdkVersion,
            dotnetDir,
            userProfileDir,
            workloadResolver
        );

        _includePreviews = parseResult.GetValue(WorkloadListCommandParser.IncludePreviewsOption);
        string userProfileDir1 = userProfileDir ?? CliFolderPathCalculator.DotnetUserProfileFolderPath;

        _workloadManifestUpdater = workloadManifestUpdater ?? new WorkloadManifestUpdater(resolvedReporter,
            _workloadListHelper.WorkloadResolver, PackageDownloader, userProfileDir1, _workloadListHelper.WorkloadRecordRepo, _workloadListHelper.Installer);
    }

    public override int Execute()
    {
        IEnumerable<WorkloadId> installedList = _workloadListHelper.InstalledSdkWorkloadIds;

        if (_machineReadableOption)
        {
            _workloadListHelper.CheckTargetSdkVersionIsValid();

            var updateAvailable = GetUpdateAvailable(installedList);
            var installed = installedList.Select(id => id.ToString()).ToArray();
            ListOutput listOutput = new(installed, [.. updateAvailable]);

            Reporter.WriteLine(JsonSerializer.Serialize(listOutput, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
        }
        else
        {
            var versionInfo = _workloadListHelper.ManifestProvider.GetWorkloadVersion();
            Reporter.WriteLine();
            var shouldPrintTable = versionInfo.IsInstalled;
            var shouldShowWorkloadSetVersion = versionInfo.GlobalJsonPath is not null ||
                versionInfo.GlobalJsonSpecifiesWorkloadSets == true ||
                WorkloadManifestUpdater.ShouldUseWorkloadSetMode(_workloadListHelper._currentSdkFeatureBand, _workloadListHelper.UserLocalPath);

            if (shouldShowWorkloadSetVersion)
            {
                if (versionInfo.GlobalJsonPath is not null)
                {
                    Reporter.WriteLine(string.Format(
                        versionInfo.IsInstalled ?
                            CliCommandStrings.WorkloadSetFromGlobalJsonInstalled :
                            CliCommandStrings.WorkloadSetFromGlobalJsonNotInstalled,
                        versionInfo.Version,
                        versionInfo.GlobalJsonPath));
                }
                else
                {
                    Reporter.WriteLine(string.Format(CliCommandStrings.WorkloadListWorkloadSetVersion, _workloadListHelper.WorkloadResolver.GetWorkloadVersion().Version ?? "unknown"));
                }

                Reporter.WriteLine();
            }

            if (shouldPrintTable)
            {
                var manifestInfoDict = _workloadListHelper.WorkloadResolver.GetInstalledManifests().ToDictionary(info => info.Id, StringComparer.OrdinalIgnoreCase);
                InstalledWorkloadsCollection installedWorkloads = _workloadListHelper.AddInstalledVsWorkloads(installedList);
                PrintableTable<KeyValuePair<string, string>> table = new();
                table.AddColumn(CliCommandStrings.WorkloadIdColumn, workload => workload.Key);
                table.AddColumn(CliCommandStrings.WorkloadManifestVersionColumn, workload =>
                {
                    var m = _workloadListHelper.WorkloadResolver.GetManifestFromWorkload(new WorkloadId(workload.Key));
                    var manifestInfo = manifestInfoDict[m.Id];
                    return m.Version + "/" + manifestInfo.ManifestFeatureBand;
                });

                table.AddColumn(CliCommandStrings.WorkloadSourceColumn, workload => workload.Value);

                table.PrintRows(installedWorkloads.AsEnumerable().OrderBy(workload => workload.Key), l => Reporter.WriteLine(l));
            }

            Reporter.WriteLine();
            Reporter.WriteLine(CliCommandStrings.WorkloadListFooter);
            Reporter.WriteLine();

            var updatableWorkloads = _workloadManifestUpdater.GetUpdatableWorkloadsToAdvertise(installedList).Select(workloadId => workloadId.ToString());
            if (updatableWorkloads.Any())
            {
                Reporter.WriteLine(string.Format(CliCommandStrings.WorkloadListWorkloadUpdatesAvailable, string.Join(" ", updatableWorkloads)));
                Reporter.WriteLine();
            }
        }

        return 0;
    }

    internal IEnumerable<UpdateAvailableEntry> GetUpdateAvailable(IEnumerable<WorkloadId> installedList)
    {
        // This was an internal partner ask, and they do not need to support workload sets.
        _workloadManifestUpdater.UpdateAdvertisingManifestsAsync(_includePreviews).Wait();
        var manifestsToUpdate = _workloadManifestUpdater.CalculateManifestUpdates();

        foreach ((ManifestVersionUpdate manifestUpdate, WorkloadCollection workloads) in manifestsToUpdate)
        {
            foreach ((WorkloadId workloadId, WorkloadDefinition workloadDefinition) in workloads)
            {
                if (installedList.Contains(workloadId))
                {
                    var existingVersion = _workloadListHelper.WorkloadResolver.GetManifestVersion(manifestUpdate.ManifestId.ToString());

                    yield return new UpdateAvailableEntry(existingVersion,
                        manifestUpdate.NewVersion.ToString(),
                        workloadDefinition.Description, workloadId.ToString());
                }
            }
        }
    }

    internal record ListOutput(string[] Installed, UpdateAvailableEntry[] UpdateAvailable);

    internal record UpdateAvailableEntry(string ExistingManifestVersion, string AvailableUpdateManifestVersion,
        string Description, string WorkloadId);
}
